package org.melanesia.dom;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Quite naive, but so far sufficient ;-) read only DOM wrapper.
 *
 * @author marcin.kielar
 */
public class Node {

    /** Wrapped w3c node. */
    private final org.w3c.dom.Node thisNode;

    /** Internal iterator for child nodes iteration. */
    private Iterator<Node> childrenIterator;

    /** Internal map of iterators for named child nodes iteration. */
    private final HashMap<String, Iterator<Node>> namedChildrenIteratorMap = new HashMap<String, Iterator<Node>>();

    /**
     * Constructs node from input stream.
     * @param inputStream input stream to parse
     * @throws SAXException  If any parse errors occur.
     * @throws IOException If any IO errors occur.
     * @throws ParserConfigurationException if a DocumentBuilder cannot be created which satisfies the configuration requested.
     */
    public Node(final InputStream inputStream) throws SAXException, IOException, ParserConfigurationException {
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream);
        thisNode = document.getDocumentElement();
    }

    /**
     * Constructs node from w3c node.
     * @param node w3c node
     */
    public Node(final org.w3c.dom.Node node) {
        thisNode = node;
    }

    /**
     * Returns all child elements of this node.
     * @return list child elements
     */
    public final List<Node> getChildren() {

        NodeList nodeList = thisNode.getChildNodes();
        List<Node> children = new ArrayList<Node>(nodeList.getLength());

        for (int i = 0, size = nodeList.getLength(); i < size; i++) {
            org.w3c.dom.Node item = nodeList.item(i);
            if (item.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
            	children.add(new Node(item));
            }
        }

        return children;
    }

    /**
     * Returns all child elements of given name.
     * @param nodeName name of the elements to return
     * @return list child elements
     */
    public final List<Node> getChildren(final String nodeName) {

        NodeList nodeList = thisNode.getChildNodes();
        List<Node> children = new ArrayList<Node>(nodeList.getLength());

        for (int i = 0, size = nodeList.getLength(); i < size; i++) {
            org.w3c.dom.Node item = nodeList.item(i);
            if (item.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && item.getNodeName().equals(nodeName)) {
                children.add(new Node(item));
            }
        }

        return children;
    }

    /**
     * Returns first child element of this node. This method creates an iterator
     * internally, which can be iterated using {@link #nextChild()}. If the
     * internal iterator already exists this method destroys it and creates a
     * new one.
     *
     * @return first child element of this node
     */
    public final Node firstChild() {
        Node node = null;
        this.childrenIterator = getChildren().iterator();

        if (this.childrenIterator.hasNext()) {
            node = this.childrenIterator.next();
        }

        if (node == null) {
            this.childrenIterator = null;
        }

        return node;
    }

    /**
     * Returns next child element of this node using internal iterator created
     * by {@link #firstChild()}. If the internal iterator has not been yet
     * created (there has been no call to {@link #firstChild()}, this method
     * creates one and returns first child. Otherwise it iterates to the next
     * element.
     *
     * @see #firstChild()
     * @return next child element of this node
     */
    public final Node nextChild() {

        Node node = null;
        if (this.childrenIterator == null) {
            node = firstChild();
        } else if (this.childrenIterator.hasNext()) {
            node = this.childrenIterator.next();
        }

        if (node == null) {
            this.childrenIterator = null;
        }

        return node;
    }

    /**
     * Returns first child element of specified name of this node. This method
     * creates an iterator internally, which can be iterated using
     * {@link #nextChild(String)}. If the internal iterator already exists this
     * method destroys it and creates a new one.
     *
     * Note, that this method creates and holds independent iterators for every
     * node name its called with. Note also, that you have to use
     * {@link #nextChild(String)} method, and not the @{link
     * {@link #nextChild()} to iterate the collection further.
     *
     *
     * @param nodeName
     *            name of the child nodes to iterate through
     * @return first child element of this node
     */
    public final Node firstChild(final String nodeName) {

        Node node = null;
        Iterator<Node> iterator = getChildren(nodeName).iterator();

        if (iterator.hasNext()) {
            node = iterator.next();
        }

        if (node != null) {
            this.namedChildrenIteratorMap.put(nodeName, iterator);
        }

        return node;
    }

    /**
     * Returns next child element of this node using internal iterator created
     * by {@link #firstChild(String)}. If the internal iterator has not been yet
     * created (there has been no call to {@link #firstChild(String)}, this
     * method creates one and returns first child. Otherwise it iterates to the
     * next element.
     *
     * @see #firstChild(String)
     * @param nodeName
     *            name of the child nodes to iterate through
     * @return next child element of given name of this node
     */
    public final Node nextChild(final String nodeName) {

        Node node = null;
        Iterator<Node> iterator = this.namedChildrenIteratorMap.get(nodeName);

        if (iterator == null) {
            node = firstChild(nodeName);
        } else if (iterator.hasNext()) {
            node = iterator.next();
        }

        if (node == null) {
            this.namedChildrenIteratorMap.remove(nodeName);
        }

        return node;
    }

    /**
     * Returns this node name.
     * @return node name
     */
    public final String getNodeName() {
        return thisNode.getNodeName();
    }

    /**
     * Returns attribute value.
     * @param name attribute name
     * @return attribute value
     */
    public final String getAttribute(final String name) {
        NamedNodeMap map = thisNode.getAttributes();
        org.w3c.dom.Node attrNode = map.getNamedItem(name);
        if (attrNode != null) {
            return attrNode.getTextContent();
        } else {
            return null;
        }
    }

    /**
     * Returns node text content.
     * @return node text content
     */
    public final String getTextContent() {
        return thisNode.getTextContent();
    }

    @Override
	public final String toString() {
    	return thisNode.getNodeName();
    }

}
